<?php

namespace liketp\database;

use liketp\App;
use liketp\exception\Exception;
use Throwable;

class Connector
{
    protected $app = null;
    protected $connections = [];
    protected $builders = [];
    protected $dbConfig = [];

    // TODO
    //protected $readConnections = [];
    //protected $writeConnections = [];
    //protected $lastConnection = null;

    protected $defaultType = 'mysql';
    protected $defaultNamespace = [
        'connection' => 'liketp\\database\\connection',
        'builder' => 'liketp\database\builder'
    ];
    protected $allowMethods = ['select', 'insert', 'insertAll', 'update', 'delete', 'query', 'execute'];


    public function __construct(App $app)
    {
        $this->app = $app;
        $this->dbConfig = $app->config->get('database');
    }

    public function connection($config)
    {
        $namespace = $this->defaultNamespace['connection'];

        $driver = $this->driver('connection', $config, $namespace);
        $config = $driver::setConfig($config);
        $flag = $this->connectionFlag($driver, $config);

        if (isset($this->connections[$flag]) == false) {
            $this->connections[$flag] = $this->app->make($driver, [$config], true);
        }

        return $this->connections[$flag];
    }

    public function builder($config)
    {
        $namespace = $this->defaultNamespace['builder'];

        $driver = $this->driver('builder', $config, $namespace);
        $flag = $driver;

        if (isset($this->builders[$flag]) == false) {
            $this->builders[$flag] = $this->app->make($driver);
        }

        return $this->builders[$flag];
    }

    public function execute($query, $method)
    {
        $getLastId = false;
        if ($method == 'insertGetId') {
            $method = 'insert';
            $getLastId = true;
        }
        if (in_array($method, $this->allowMethods) == false) return;

        $config = $query->getOptions('connect');

        $connection = $this->connection($config);
        $builder = $this->builder($config);

        if (in_array($method, ['query', 'execute'])) {
            $sql = $query->getOptions('sql', '');
            $params = $query->getBindData();
        } else {
            $sql = $builder->$method($query);
            $params = $query->getBindData();
        }

        if ($query->getOptions('fetchSql', false)) {
            return $builder->buildSql($sql, $params);
        }

        try {
            // select, query
            if (in_array($method, ['select', 'query'])) {
                $data = $connection->select($sql, $params);
            }
            // delete, update
            else if (in_array($method, ['delete', 'update'])) {
                $data = $connection->execute($sql, $params, 'row_count');
            }
            // other: insert, insertGetId, select, execute...
            else {
                if ($getLastId) {
                    $pdo = $connection->execute($sql, $params, 'pdo');
                    $data = $pdo->lastInsertId();
                } else {
                    $data = $connection->execute($sql, $params);
                }
            }
        } catch (Throwable $e) {
            trace(['sql' => $sql, 'params' => json_encode($params)], 'sql');
            throw new Exception($e->getMessage());
        }

        $this->dbConfig['debug'] && event('db.record_sql', $builder->buildSql($sql, $params));

        return $data;
    }

    public function debug($query, $method)
    {
        if (in_array($method, $this->allowMethods) == false) {
            throw new Exception('the param [method] is invalidate');
        }

        $query = $query->createQuery($query, ['*']);
        $config = $query->getOptions('connect');
        $builder = $this->builder($config);

        $sql = $builder->$method($query);
        $params = $query->getBindData();

        return compact('sql', 'params');
    }


    //------------------------------------------------ protected
    protected function driver(string $driverType, array $config, $namespace = '')
    {
        if (not_in_array($driverType, ['connection', 'builder'])) return '';

        $driver = config_get($config, 'driver.' . $driverType);
        if (empty($driver)) {
            $namespace = rtrim($namespace, '\\') . '\\';
            $type = config_get($config, 'type', $this->defaultType);
            $driver = $namespace . str_to_camel($type, true);
        }
        return $driver;
    }

    protected function connectionFlag(string $driver, array $config)
    {
        return $driver::flag($config);
    }
}
